當我們設計頁面發現有些元件的組合重複出現時,雖然可以直接「複製—貼上」,但是如果使用範本插入,會更符合物件導向設計中的「重用」原則 (或 DRY)。而且未來若是範本修改,所有插入範本的頁面也都自然跟著更新,若是原本採用「複製—貼上」,就得一個一個頁面去搜尋修改。
假設我們發現 <textbox>
+ <button>
這個 pattern 經常出現,就可以把它定義成一個範本並指定一個不重複的名字,光是定義範本並不會顯示在頁面上,而是要用 <apply>
插入。
<apply template="one-field"/>
<template name="one-field">
<textbox/><button label="送出" style="margin-left: 5px"/>
</template>
範本可以定義在頁面上任何地方,<apply>
都可以透過指定範本名稱來插入,ZK 會將範本內定義的片段插入到 <apply>
的位置。
若我事先在範本內指定幾個 EL 變數,插入的時候根據不同的情境再傳入不同的值的話,可使範本可應用的情境更廣,變得更模組化。
例如我可以把「提示訊息」、「按鈕文字」作為參數輸入,這樣每次使用時該範本時,我都可以調成不同的文字:
<apply template="one-field2" hint="請輸入:" label="OK"/>
<template name="one-field2">
${hint}<textbox/><button label="${label}" style="margin-left: 5px"/>
</template>
hint
, label
,對應到範本內的2個變數。不過這種方式傳的參數並沒有編譯器幫你檢查名稱或數量,要自己檢查避免打錯字同一份資料有時候有不同的呈現方式,例如個人資料,可以是「顯示模式」或「編輯模式」,就可以透過切換兩種不同的範本來做到模式切換效果。
假設我要在呈現英雄的個人資料畫面做出可以立即切換成編輯模式的編輯器:
閱讀模式
編輯模式
首先先定義兩種範本:normal, edit
<template name="normal">
<label value="${hero.id}"/>
<label value="${hero.name}"/>
<label value="${hero.age}"/>
</template>
<template name="edit">
<label value="${hero.id}"/>
<textbox id="nameBox" value="${hero.name}" width="100%"/>
<intbox id="ageBox" value="${hero.age}"/>
</template>
再透過按鈕去切換不同的範本:
<div style="width: 50%" apply="quickstart.shadow.ProfileEditorComposer">
<button label="Edit"/>
<vlayout style="border: solid 2px; border-radius:5px; width: 150px; padding:5px; margin: 5px">
<apply id="profile" template="normal" hero="${hero}" dynamicValue="true"/>
</vlayout>
</div>
public class ProfileEditorComposer extends SelectorComposer {
@Wire("::shadow#profile")
private Apply apply;
@Wire
來取得參考,selector 語法為 ::shadow
代表所有的 shadow 元件,加上 # ID 選擇器,代表 ID 為 profile 的 shadow 元件@Override
public void doBeforeComposeChildren(Component comp) throws Exception {
super.doBeforeComposeChildren(comp);
hero = HeroService.create("奇異博士");
Sessions.getCurrent().setAttribute("hero", hero);
}
doBeforeComposeChildren()
也是一個生命週期方法,根據名稱就可得知它是在子元件被建構前被呼叫。這次我覆寫 doBeforeComposeChildren()
是因為我要在範本中的元件產生前就把資料準備好,這樣 EL 才能存取到 attribute。Sessions.getCurrent()
是 ZK 提供的方法,讓我可以取得現有 request 所屬的 session。請注意我存入的鍵值為 hero
,也就是我在 zul 上用 EL 存取的變數名。<apply id="profile" template="normal" hero="${hero}" dynamicValue="true"/>
${hero}
把參數傳入範本,那這個變數定義在哪呢?ZK 解析 EL 變數時,會從較小的範圍(scope) 一直解析到大的範圍,因此若是 zscript 中沒有定義 hero 這個變數,ZK 會持續在 session, application 中的 attribute 尋找 key 值為 hero 的物件實作點擊按鈕來切換模式
private boolean isEdit = false;
@Listen("onClick = button")
public void switchMode(){
if (isEdit){
hero.setName(((Textbox)modeButton.getFellow("nameBox")).getValue());
hero.setAge(((Intbox)modeButton.getFellow("ageBox")).getValue());
}
isEdit = !isEdit;
modeButton.setLabel(isEdit ? "Save": "Edit");
apply.setTemplate(isEdit ? "edit" : "normal");
apply.recreate();
}
@Listen
註冊 onClick 傾聽器setTemplate()
來切換不同的範本,並呼叫 recreate()
來重建範本內容另一種應用是將範本定義為一個佈局範本,然後在每個頁面中插入,可以使得頁面的佈局一致。例如我設計一個由分成標頭、內容、頁尾3部分的佈局範本:
layout-template.zul
<borderlayout height="100%">
<north size="20%" style="background-color:#5c5480">
<div sclass="center">header</div>
</north>
<center style="background-color:#738096">
<apply template="content"/>
</center>
<south size="5%" style="background-color:#5c5480">
<div sclass="center">footer</div>
</south>
</borderlayout>
content
當要插入該佈局範本時,才定義 content
這個範本的內容:
<apply templateURI="layout-template.zul">
<template name="content">
<div sclass="center">本頁面內容</div>
</template>
</apply>
這樣就可以做到,既維持頁面在同一個3分佈局,又能彈性決定每個頁面的內容。
以上幾個例子可以幫你更好的重用你的頁面,避免「複製—貼上」這種容易產生 bug 的行為。